function PetriNetView() {
	this.dom = {
		place: [],
		transition: [],
		connection: [],
		token: []
	}
	this.domDataSet = {
		place: {},
		transition: {},
		connection: {},
		token: {}
	}
}
void function($) {
var fn = PetriNetView.prototype;

fn.move = function(p, dis_x, dis_y, field) {
	var self = this;
	field = field || null;
	var pos = self.getPosition(p);
	if (field) {
		pos = pos[field];
	}
	pos[0] += dis_x;
	pos[1] += dis_y;
	return self;
}

fn.get = function(p) {
	return p && this[p.type].get(p);
}

fn.create = function(p) {
	var self = this, $div = self.get(p);
	if ($div) {
		return $div;
	}
	$div = self[p.type].render(p);
	$div.data('object', p).addClass(p.type);
	self.dom[p.type].push($div);
	$div.appendTo(self.container);
	self[p.type].fresh($div);
	return $div;
}

fn.remove = function(p) {
	var self = this;
	var $div = self.get(p);
	if ($div) {
		$div.remove();
		var i = self.dom[p.type].indexOf($div);
		self.dom[p.type].splice(i, 1);
		self[p.type].afterRemove(p);
	}
	return self;
}

fn.fresh = function(p) {
	var self = this, $div = self.get(p);
	if (p.isRemoved) {
		return self.remove(p);
	}
	if ($div) {
		self[p.type].fresh($div);
	}
	return self;
}

fn.freshPosition = function(p) {
	var self = this, $div = self.get(p);
	if ($div) {
		self[p.type].freshPosition($div);
	}
	return self;
}

fn.render = function() {
	var self = this, petri = self.petri;

	var nodes = [].concat(petri.objects.place, petri.objects.transition, petri.objects.token, petri.objects.connection);
	nodes.forEach(function(o) {
		self.create(o);
	})

	return self;
}

fn.getPosition = function(p) {
	var self = this, $div = self.get(p);
	if ($div) {
		return self[p.type].getPosition($div);
	}
	return [0, 0];
}

fn.init = function(option) {
	var self = this;
	self.petri = option.petri;
	self.container = option.container;
	var fields = ['place', 'transition', 'token', 'connection'];
	fields.forEach(function(field) {
		self[field] = {
			get: function(p) {
				return self.dom[field].find(function($div) {
					return $div.data('object') == p;
				})
			},
			fresh: function() {

			},
			freshPosition: function() {

			},
			afterRemove: function() {

			},
			render: function(p) {
				return $("<div></div>")
				.addClass(p.type)
				.data(self.domDataSet[field][p.name] || {'pos': [0, 0]})
				.attr('title', p.name);
			},
			getPosition: function($div) {
				return $div.data('pos');
			}
		}
	})
	self.transition.freshPosition = self.place.freshPosition = function($div) {
		var pos = $div.data('pos'), p = $div.data('object');
		$div.css({
			left: pos[0] + 'px',
			top: pos[1] + 'px',
			transform: 'translate(-50%,-50%)'
		})
		var petri = self.petri;
		var connections = [].concat(petri.connection.filter('from', p), petri.connection.filter('to', p));
		connections.forEach(function(c) {
			self.fresh(c);
		})
		return this;
	}

	self.transition.afterRemove = self.place.afterRemove = function(p) {
		var petri = self.petri;
		var connections = [].concat(petri.connection.filter('from', p), petri.connection.filter('to', p));
		connections.forEach(function(c) {
			self.remove(c);
		})
		if (p.type == 'place') {
			p.selfTokens().forEach(function(p) {
				self.remove(p);
			})
		}
		return this;
	}

	self.connection.afterRemove = function(c) {
		if (c.to && c.to.type == 'transition') {
			self.fresh(c.to);
		}
		return this;
	}

	self.transition.fresh = function($div) {
		this.freshPosition($div);
		var p = $div.data('object');
		$div.attr('title', p.name);
		if (p.isEnable()) {
			$div.addClass('enable');
		} else {
			$div.removeClass('enable');
		}
		return this;
	};
	self.place.fresh = function($div) {
		this.freshPosition($div);
		var petri = self.petri,
			p = $div.data('object'),
			i = p.selfTokens().length;
		$div.attr('title', p.name);
		if (i == 1) {
			$div.addClass('length-one').text('');
		} else {
			$div.removeClass('length-one').text(i || '');
		}
		petri.connection.filter('from', p).forEach(function(c) {
			c.to && self.fresh(c.to);
		})
		return this;
	}
	self.connection.fresh = function($div) {
		var c = $div.data('object');
		var fo = c.from, to = c.to;
		var f = self.getPosition(fo), t = self.getPosition(to);
		$div.data({
			'from': f,
			'to': t
		}).attr('title', c.count > 1 ? c.count : '');
		this.freshPosition($div);
		return this;
	}
	self.connection.getPosition = function($div) {
		return {
			from: $div.data('from'),
			to: $div.data('to')
		}
	}
	var jd_hd = Math.PI * 2 / 360;
	self.connection.freshPosition = function($div) {
		var to = $div.data('object').to, $to = self.get(to);
		var f = $div.data('from'), t = $div.data('to');
		var chevronWidth = 0, width = 0, degree = 0;
		if (f[0] == t[0]) {
			// 垂直于x轴
			width = t[1] - f[1];
			degree = 90;
		} else {
			degree = Math.atan((t[1] - f[1]) / (t[0] - f[0])) / jd_hd;
			width = (t[0] - f[0]) / Math.cos(degree * jd_hd);
		}
		if (!to) {
			chevronWidth = 0;
		} else if (to.type == 'place') {
			chevronWidth = $to ? $to.width() / 2 : 0;
		} else {
			chevronWidth = $to ?
				Math.abs(($to.width() / 2) / (Math.abs(degree) <= 45 ? Math.cos(degree * jd_hd) : Math. sin(degree * jd_hd)))
			   : 0;
		}
		if (width < 0) {
			width *= -1;
			degree = 180 + degree;
		}
		$div.width(width).css({
			left: f[0] + 'px',
			top: f[1] + 'px',
			transformOrigin: "0 50%",
			transform: 'translateY(-50%) rotate(' + degree + 'deg)'
		}).find('.chevron').css({
			'transform': 'translateX(-' +  chevronWidth + 'px)'
		})
		return this;
	}
	self.connection.render = function(p) {
		return $("<div><div class='menu chevron glyphicon glyphicon-chevron-right'></div></div>").data(
		self.domDataSet.connection[p.name] || {
			'from': [0, 0],
			'to': [0, 0]
		});
	}
	return self;
}

fn.clear = function() {
	var self = this,
	fields = ['place', 'transition', 'token', 'connection'];
	fields.forEach(function(f) {
		self.dom[f].slice().forEach(function($dom, index) {
			var obj = $dom.data('object');
			self.remove(obj);
		})
	})
	self.domDataSet = {
		place: {},
		transition: {},
		connection: {},
		token: {}
	}
	return self;
}

fn.freshDomDataSet = function() {
	var self = this,
	fields = ['place', 'transition', 'token', 'connection'];
	self.domDataSet = {
		place: {},
		transition: {},
		connection: {},
		token: {}
	}
	fields.forEach(function(f) {
		self.dom[f].forEach(function($dom) {
			var obj = $dom.data('object');
			var dataset = {};
			$.extend(dataset, $dom.data());
			delete dataset.object;
			self.domDataSet[f][obj.name] = dataset;
		})
	})
	return self;
}

fn.useDomDataSet = function() {
	var self = this,
	fields = ['place', 'transition', 'token', 'connection'];
	fields.forEach(function(f) {
		self.dom[f].forEach(function($dom) {
			var obj = $dom.data('object');
			if (self.domDataSet[f][obj.name]) {
				$dom.data(self.domDataSet[f][obj.name]);
			}
		})
	})
	return self;
}

fn.toJSON = function() {
	var self = this;
	return {
		domDataSet: self.domDataSet
	};
}

fn.parse = function(str) {
	var self = this;
	self.domDataSet = typeof str == 'string' ? JSON.parse(str).domDataSet : str.domDataSet;
	return self;
}

fn.cloneDomDataSet = function(ptv) {
	var self = this;
	ptv = ptv || self;
	var ds = {};
	$.extend(true, ds, ptv.domDataSet);
	self.domDataSet = ds;
	return self;
}

}(jQuery)